﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Media;
using System.Diagnostics;
using LogFileScanner.Properties;

using WeifenLuo.WinFormsUI.Docking;

namespace LogFileScanner
{
    public partial class StreamViewer : DockContent, LogFileScanner.Viewer, LogFileScanner.Managed
    {
        private FileInfo fileToStream;
        private long lineCount = 0;
        private string command;

        public StreamViewer()
        {
            InitializeComponent();
        }

        #region Public Interface

        public delegate void StreamClosed(StreamViewer viewer);

        public event Utility.VoidMethodDelegate NotifyProgressStart;
        public event Utility.VoidMethodDelegate NotifyProgressStop;
        public event Utility.MessageMethodDelegate NotifyLogMessage;
        public event ProcessViewer.ProcessStarted NotifyProcessStarted;
        public event ProcessViewer.ProcessFinished NotifyProcessFinished;
        public event StreamViewer.StreamClosed NotifyStreamClosed;
        
        public FileInfo FileToStream
        {
            get
            {
                return fileToStream;
            }
            set
            {
                this.fileToStream = value;
                UpdateInfo();
            }
        }

        public string Command { 
            get 
            {
                return command; 
            }
            set 
            {
                this.command = value;
                UpdateInfo();
            }
        }

        private void UpdateInfo()
        {
            string fileName = "[none]";

            if (fileToStream != null)
            {
                fileName = fileToStream.Name;
            }

            this.Text = string.Format("{0} {1}", Command, fileName);

            textContents.Clear();
            printCurrentSettings();
            buttonExecute.Enabled = this.fileToStream != null && !string.IsNullOrEmpty(Command);

        }

        public string Arguments {
            get
            {
                return this.textArguments.Text;
            }
            set
            {
                this.textArguments.Text = value;
                UpdateInfo();
            }
        }

        public void Terminate()
        {
            this.Close();
        }
        
        #endregion

        #region Event Callback

        private void processStart()
        {
            if (NotifyProgressStart != null)
            {
                NotifyProgressStart();
            }
        }

        private void processStop()
        {
            if (NotifyProgressStop != null)
            {
                NotifyProgressStop();
            }
        }


        private void logMessage(string value, Utility.MessageLevel level)
        {
            if (NotifyLogMessage != null)
            {
                NotifyLogMessage(value, level);
            }
        }

        private void processStarted(Process process)
        {
            if (NotifyProcessStarted != null)
            {
                NotifyProcessStarted(process);
            }
        }

        private void processFinished(Process process)
        {
            if (NotifyProcessFinished != null)
            {
                NotifyProcessFinished(process);
            }
        }


        private void streamClosed()
        {
            if (NotifyStreamClosed != null)
            {
                NotifyStreamClosed(this);
            }
        }

        #endregion 

        #region UI Event

        private void StreamViewer_FormClosing(object sender, FormClosingEventArgs e)
        {
            backgroundWorker.CancelAsync();
        }

        private void StreamViewer_FormClosed(object sender, FormClosedEventArgs e)
        {
            streamClosed();
        }

        private void textSearch_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\r')
            {
                e.Handled = true;
                searchInContents(textSearch.Text);
            }
        }

        private void textCommand_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\r')
            {
                e.Handled = true;
                Stream();
            }
        }

        private void buttonSearch_Click(object sender, EventArgs e)
        {
            searchInContents(textSearch.Text);
        }

        private void textContents_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Control && e.KeyCode == Keys.F)
            {
                textSearch.Focus();
            }
            else if (textSearch.Text != "" && e.KeyCode == Keys.N)
            {
                searchInContents(textSearch.Text);
            }
            else if (e.KeyCode == Keys.C && e.Control) 
            {
                logMessage("Open Processes [F5] to terminate the process", Utility.MessageLevel.WARN);
            }
        }

        private void buttonGrep_Click(object sender, EventArgs e)
        {
            Stream();
        }


        private void textSearch_TextChanged(object sender, EventArgs e)
        {
            textContents.Text = textContents.Text;
        }

        private void textContents_LinkClicked(object sender, LinkClickedEventArgs e)
        {
            OpenExternalEditor(e.LinkText);
        }

        #endregion
        
        #region Helper Methods

        public void Stream()
        {
            if (backgroundWorker.IsBusy)
            {
                buttonExecute.Text = "&Start";
                logMessage(string.Format("Canceling Grep at user's request"), Utility.MessageLevel.WARN);
                backgroundWorker.CancelAsync();
            }
            else
            {
                processStart();

                textContents.Clear();
                buttonExecute.Text = "&Stop process...";
                logMessage(string.Format("Running Grep for {0}", Command), Utility.MessageLevel.INFO);
                backgroundWorker.RunWorkerAsync();
            }
        }

        private void printCurrentSettings()
        {
            if (string.IsNullOrEmpty(Command))
            {
                appendLine("Process Statement is empty, nothing to execute");
                return;
            }


            appendLine("Process:");
            if (!string.IsNullOrEmpty(Command))
            {
                appendLine("\t" + Command);
            }
            else
            {
                appendLine("\t[ No command Loaded ]");
            }

            string fullName = "[Not Set]";

            if (fileToStream != null)
            {
                fullName = fileToStream.FullName;
            }

            string commandLineArguments = string.Format(Arguments, fullName);
            appendLine("Arguments: ");
            if (!string.IsNullOrEmpty(commandLineArguments))
            {
                appendLine("\t" + commandLineArguments);
            }
            else
            {
                appendLine("\t[ No statement Loaded ]");
            }
        }

        private void searchInContents(string value)
        {
            if (value == null)
            {
                return;
            }

            int startPosition = textContents.SelectionStart;

            int index = -1;

            if (textContents.TextLength > startPosition)
            {
                if (startPosition > -1)
                {
                    index = textContents.Find(value, startPosition + 1, RichTextBoxFinds.MatchCase);
                }
                else
                {
                    index = textContents.Find(value);
                }
            }

            if (index < 0)
            {
                logMessage(string.Format("Text not found: {0}.", value), Utility.MessageLevel.WARN);
            }
            else
            {
                textContents.Select(index, value.Length);
                textContents.SelectionBackColor = Color.Yellow;

                textContents.ScrollToCaret();

                textSearch.Focus();
            }
        }

        private void OpenExternalEditor(string filePath)
        {
            string commandLineArguments = string.Format(AppSettings.EditorArguments, filePath);

            try
            {
                System.Diagnostics.Process.Start(AppSettings.EditorPath, commandLineArguments);
            }
            catch (Exception ex)
            {
                logMessage(string.Format("Unable to open file {0}.\n\tUnable to start \"{1}\"\n\tWith arguments: {2}.\n\tCause: {3}",
                                filePath, AppSettings.EditorPath, commandLineArguments, ex.Message), Utility.MessageLevel.ERROR);
            }
        }

        #endregion

        #region Background Worker

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                clearContents();
                printCurrentSettings();

                if (fileToStream != null)
                {
                    FileInfo file = fileToStream;
                    string commandLineArguments = string.Format(Arguments, file.FullName);

                    try
                    {
                        using (Process process = new Process()) {
                            
                            process.StartInfo.UseShellExecute = false;
                            process.StartInfo.FileName = Command;
                            process.StartInfo.RedirectStandardOutput = true;
                            process.StartInfo.RedirectStandardError = true;
                            process.StartInfo.Arguments = commandLineArguments;
                            process.StartInfo.CreateNoWindow = true;

                            process.Start();
                            
                            // let the process manager track this process
                            processStarted(process);
                            
                            appendLine("");
                            appendLine("===================================================", Color.Blue);
                            appendLine(string.Format("\tStarting {0} for {1}", process.StartInfo.FileName, file.FullName), Color.Blue);
                            appendLine("===================================================", Color.Blue);
                            
                            using (StreamReader stream = process.StandardOutput) {
                                while (!process.HasExited || !stream.EndOfStream)
                                {
                                    if (backgroundWorker.CancellationPending)
                                    {
                                        appendLine("");
                                        appendLine("===================================================", Color.Blue);
                                        appendLine(string.Format("\t{0} was interupted per users's request at line {1}", Command, lineCount), Color.Blue);
                                        appendLine("===================================================", Color.Blue);

                                        return;
                                    }

                                    string line = stream.ReadLine();

                                    appendLine(line);
                                }
                            }

                            using (StreamReader errorStream = process.StandardError) 
                            {
                                while (!errorStream.EndOfStream)
                                {
                                    if (backgroundWorker.CancellationPending)
                                    {
                                        appendLine("");
                                        appendLine("===================================================", Color.Blue);
                                        appendLine(string.Format("\t{0} was interupted per users's request at line {1}", Command, lineCount), Color.Blue);
                                        appendLine("===================================================", Color.Blue);

                                        return;
                                    }

                                    string line = errorStream.ReadLine();

                                    appendLine(line, Color.Red);
                                }
                            }

                            // process manager know that we are done
                            processFinished(process);
                        }
                    }
                    catch (Exception ex)
                    {
                        appendLine(string.Format("\tError reading executing process \"{0}\"", Command), Color.Red);
                        appendLine(string.Format("\tWith arguments: {0}", commandLineArguments), Color.Red);
                        appendLine(string.Format("\tDue to error: {0}", ex.Message), Color.Red);
                    }

                }
            }
            catch (Exception ex)
            {
                logMessage(string.Format("Unable to load file due to {0}", ex.Message), Utility.MessageLevel.ERROR);
            }
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            buttonExecute.Text = "&Start";
            logMessage(string.Format("Loaded {0} lines.", lineCount), Utility.MessageLevel.INFO);

            processStop();
        }

        #endregion

        #region Callback methods

        private delegate void stringMethod(string line);
        private delegate void stringColorMethod(string line, Color color);
        private delegate void voudMethod();

        private void clearContents()
        {
            if (InvokeRequired)
            {
                this.Invoke((voudMethod)clearContents);
                return;
            }

            lineCount = 0;

            textContents.Clear();
            labelLineCount.Text = lineCount.ToString();
        }

        private void appendLine(string line)
        {
            if (InvokeRequired)
            {
                this.Invoke((stringMethod)appendLine, line);
                return;
            }

            lineCount++;

            textContents.AppendText(line + Environment.NewLine);
            labelLineCount.Text = lineCount.ToString();
        }

        private void appendLine(string line, Color color)
        {
            if (InvokeRequired)
            {
                this.Invoke((stringColorMethod)appendLine, line, color);
                return;
            }

            int start = textContents.TextLength;

            appendLine(line);
            
            textContents.SelectionStart = start;
            textContents.SelectionLength = line.Count();
            textContents.SelectionColor = color;
        }

        #endregion

        #region Settings IO

        private Settings AppSettings
        {
            get { return Settings.Default; }
        }

        #endregion

    }
}
